<body></body>
<script type="module">
	// 渲染器
	function createRenderer(optios) {
		const { createElement, setElementText, insert, patchProps } = optios

		function shouldSetAsProps(el, key, value) {
			if (key === 'form' && el.tagName.toLowerCase() === 'input') return false
			return key in el
		}

		// 挂载函数
		function mountElement(vnode, container) {
			const el = (vnode.el = createElement(vnode.type)) // 让 vnode.el 存一份 真实 DOM 元素 对的指针 ，用于回收元素
			const children = vnode.children // children处理

			if (typeof children === 'string') {
				// 如果节点值是string，设置文本
				setElementText(el, children)
			} else if (Array.isArray(children)) {
				// children是一个数组，遍历这个数组，数组每个元素都是一个vnode
				// 调用patch将他们挂载上去. oldVNode=null, container = el
				children.forEach((child) => {
					patch(null, child, el)
				})
			}

			// 设置 vnode 属性
			if (vnode.props) {
				for (const key in vnode.props) {
					const value = vnode.props[key]
					patchProps(el, key, null, value)
				}
			}

			insert(el, container)
		}

		function patch(n1, n2, container) {
			// 旧的节点与新的节点的类型不同，需要卸载 与 回收n1
			if (n1 && n1.type !== n2.type) {
				unmont(n1)
				n1 = null
			}
			// 新的和旧的根节点一样，证明 新的节点 与 旧的节点描述一致
			const { type } = n2
			if (typeof type === 'string') {
				// 组件的类型是字符串型，他描述的是普通标签元素
				if (!n1) {
					mountElement(n2, container)
				} else {
					patchElement(n1, n2)
				}
			} else if (typeof type === 'object') {
				// 如果type的值类型是对象，那么他描述的是一个组件
			} else if (typeof type === 'xxx') {
				// 其他
			}
		}

		function unmont(vnode) {
			const el = vnode.el // 使用前面存旧的元素
			const parent = el.parentNode //获取父元素去移除元素
			if (parent) parent.removeChild(el)
		}

		function render(vnode, container) {
			if (vnode) {
				patch(container._vnode, vnode, container)
			} else {
				if (container._vnode) {
					unmont(container._vnode)
				}
			}
			container._vnode = vnode // container.vnode代表旧的vnode
		}
		return { render }
	}

	// 渲染器
	const renderer = createRenderer({
		createElement(tag) {
			return document.createElement(tag)
		},
		setElementText(el, text) {
			el.textContent = text
		},
		insert(el, parent, anchor = null) {
			parent.insertBefore(el, anchor)
		},
		patchProps(el, key, previousValue, nextValue) {
			if (key === 'class') {
				el.className = nextValue
			} else if (shouldSetAsProps(el, key, nextValue)) {
				const type = typeof el[key] //  获取该 DOM 属性值 的类型

				if (type === 'boolean' && nextValue === '') {
					el[key] = true // 如果是dom使用标准是布尔值，并且当前nextValue是空，将值矫正为true
				} else {
					el[key] = nextValue
				}
			} else {
				el.setAttribute(key, nextValue)
			}
		}
	})

	renderer.render({ type: 'p' }, document.body)

	setTimeout(() => {
		renderer.render({ type: 'input' }, document.body)
	}, 1000)
</script>
